home *** CD-ROM | disk | FTP | other *** search
/ Chip 2003 October / Chip Ekim 2003.iso / prog / code / contr / setup.exe / Disk1 / data1.cab / Configuration_En / Commands / Clean Up HTML.js < prev    next >
Encoding:
JavaScript  |  2003-07-18  |  39.9 KB  |  1,209 lines

  1. //
  2. // Copyright 2000, 2001, 2002, 2003 Macromedia, Inc. All rights reserved.
  3. // ----------------------------------------------------
  4. //
  5. // Clean Up HTML.js
  6. //
  7. // This command cleans up certain categories of superfluous
  8. // HTML within the user document without effecting document
  9. // layout.  This command makes two passes over the users
  10. // document depending on the options selected; see
  11. // cleanUpDocument() for more details.
  12. //
  13. // Version 3.0
  14. // Added new array class for cleanup HTML contains function.
  15. // ----------------------------------------------------
  16.  
  17. //
  18. // Global variables -- see initialize() for more comments.
  19. // Dreamweaver doesn't currently initialize any globals
  20. // loaded in auxilliary scripts through <SCRIPT SRC=..>,
  21. // so we explicitly initialize these initialize().
  22. //
  23. var helpDoc = MM.HELP_cmdCleanUpHTML;
  24.  
  25. var cbRemoveEmptyTags;
  26. var cbRemoveRedundant;
  27. var cbCombineFonts;
  28. var cbRemoveTags;
  29. var cbRemoveComments;
  30. var cbRemoveDWComments;
  31. var cbShowLog;
  32. var tbTagsToRemove;
  33. var numEmptyRemoved;
  34. var numRedundantRemoved;
  35. var numTagsRemoved;
  36. var numCommentsRemoved;
  37. var numFontsCombined;
  38. var arrTagsToRemove = new Array(); // Array
  39. var strClassAttrib;
  40. var strStyleAttrib;
  41. var arrDWCommentTags = new Array();
  42. var arrDWCommentNonTags = new Array();
  43. var arrDWTagPrefixes = new Array();
  44. var emptyRemovalCandidates = new Array();
  45. var redundantTagCandidates = new Array();
  46. var combinableTagCandidates = new Array();
  47. var bPreserveEmptyHeader;
  48. var bRemovedTracing;
  49. var arrXHTMLresults;
  50.  
  51.  
  52. //------------------ Commands API --------------------
  53.  
  54. function commandButtons()
  55. {
  56.    return new Array( MM.BTN_OK,     "cleanUpDocument()"  // main entry point
  57.                    , MM.BTN_Cancel, "window.close()"
  58.                    , MM.BTN_Help,   "displayHelp()");
  59. }
  60.  
  61. function canAcceptCommand()
  62. {
  63.   var retVal = false;
  64.   if (dw.getDocumentDOM() && dw.getDocumentDOM().getParseMode() == 'html' && (dw.getFocus() == 'document' || dw.getFocus(true) == 'html' || dw.getFocus() == 'textView')){
  65.     retVal = true;
  66.   }
  67.   return retVal;
  68. }
  69.  
  70. //
  71. // ------- Local Clean Up command functions ----------
  72. //
  73.  
  74. // Return true if curArr contains an entry equal to item.
  75. function arrayContains( curArr, item )
  76. {
  77.    var nElements = curArr.length;
  78.    for( var i = 0; i < nElements; i++ )
  79.       if ( curArr[i] == item )
  80.          return true;
  81.  
  82.    return false;
  83. }
  84.  
  85. // Return true if curArr contains an entry that is a string prefix of item.
  86. function arrayContainsPrefix( curArr, item )
  87. {
  88.    var itemLength = item.length;
  89.    var nElements = curArr.length;
  90.    for( var i = 0; i < nElements; i++ )
  91.    {
  92.       var curItem = curArr[i];
  93.       if ( curItem.length <= itemLength &&
  94.          curItem == item.substring(0, curItem.length) )
  95.          return true;
  96.    }
  97.  
  98.    return false;
  99. }
  100.  
  101. function isQuote( c )
  102. {
  103.    return( c == '\"' || c == '\'' );
  104. }
  105.  
  106. function isAlpha( c )
  107. {
  108.    var isoval = c.charCodeAt(0);
  109.    return( (isoval >= "A".charCodeAt(0) && isoval <= "Z".charCodeAt(0)) ||
  110.            (isoval >= "a".charCodeAt(0) && isoval <= "z".charCodeAt(0)));
  111. }
  112.  
  113. // Match a <xxx or </xxx tag; note that xxx must be alphabetical
  114. function isTagBegin( currentchar, nextchar )
  115. {
  116.    return( currentchar == '<' && (isAlpha( nextchar ) || nextchar == '/') );
  117. }
  118.  
  119. // Note that '>' should be ignored within quotes inside tag brackets
  120. function isTagEnd( c )
  121. {
  122.    return( c == '>' );
  123. }
  124.  
  125. function isWhite( c )
  126. {
  127.    return( c == ' ' || c == '\t' || c == '\n' || c == '\r' );
  128. }
  129.  
  130. function isAllWhite( str )
  131. {
  132.    for( var i = 0; i < str.length; i++ )
  133.    {
  134.       if ( !isWhite( str.charAt( i ) ) )
  135.          return( false );
  136.    }
  137.  
  138.    return( true );
  139. }
  140.  
  141. // parseAttributes()
  142. //
  143. // Parse the attributes from within the start tag of a given
  144. // node per the rules found here: http://www.w3.org/TR/WD-html-lex/
  145. //
  146. // Return an array of arrays (unfortunately; the associative
  147. // aspect of arrays is overloaded with "instance properties",
  148. // so arrays already contain prototype methods/properties
  149. // value pairs):
  150. //
  151. //          arr[0] --> attributes array
  152. //          arr[1] --> values array
  153. //
  154. // The value for a given attribute are in the same position
  155. // at the attribute within the values array.  Singleton name
  156. // tokens have an empty ("") or undefined value.
  157. //
  158. // If bStripQuotes is true, then any "outer" quotes around an
  159. // attribute value are stripped, e.g., the value in
  160. //
  161. //    NAME="bob's name"
  162. //
  163. // is returned as: bob's name.  If bStripQuotes is false, that
  164. // value is returned as "bob's name"
  165. //
  166. // If bMakeUpper is true, all attribute/value strings are normalized
  167. // to upper case
  168. //
  169. function parseAttributes( node, bStripQuotes, bMakeUpper )
  170. {
  171.    var   tagstr         = node.outerHTML;
  172.    var   pos            = 0;
  173.    var   prevChar       = null;
  174.    var   currentChar    = null;
  175.    var   currentQuote   = null;
  176.    var   arrAttribs     = new Array();
  177.    var   arrValues      = new Array();
  178.    var   arrIdx         = 0;
  179.    var   attrib         = "";
  180.    var   value          = "";
  181.    var   bValueIsEmpty  = false;
  182.    var   bInsideQuote   = false;
  183.    var   bAccumValue    = false;
  184.    var   bAttribReady   = false;
  185.    var   bSkipToWhite   = true;  // initially true to skip "<tag "
  186.  
  187.    while( pos < node.outerHTML.length )
  188.    {
  189.       prevChar     = currentChar;
  190.       currentChar  = tagstr.charAt( pos++ );
  191.  
  192.       // Handle quote state; remember actual quote that
  193.       // flipped the state so we match ' and " right
  194.       //
  195.       if ( isQuote( currentChar ) )
  196.       {
  197.          if ( bInsideQuote )
  198.          {
  199.             if ( currentChar == currentQuote )
  200.             {
  201.                // Coming out of quoted region; turn quotes off
  202.                bInsideQuote = false;
  203.                currentQuote = null;
  204.                if ( bStripQuotes )
  205.                {
  206.                   // Careful; make sure ATTR="" works even when we're
  207.                   // stripping quotes off values
  208.                   MM_assert( bAccumValue, MSG_ParseErrEndQuote );
  209.                   bValueIsEmpty = true;
  210.                   continue;
  211.                }
  212.             }
  213.          }
  214.          else
  215.          if ( bAccumValue && value == "" ) // only turn quotes on after '=' and
  216.          {                                 // before accumulating anything; e.g.,
  217.             // Turn quotes on              // ignore the quote in ATTR=xxx"xxx
  218.             bInsideQuote = true;
  219.             currentQuote = currentChar;
  220.             if ( bStripQuotes )
  221.                continue;
  222.          }
  223.       }
  224.  
  225.       // Handle the terminating character; write any attribute/value
  226.       // we may have been accumulating and we're done.
  227.       //
  228.       if ( !bInsideQuote && isTagEnd( currentChar ) )
  229.       {
  230.          if ( attrib != "" )
  231.          {
  232.             arrAttribs[ arrIdx ]  = bMakeUpper ? attrib.toUpperCase() : attrib;
  233.             arrValues[ arrIdx++ ] = bMakeUpper ? value.toUpperCase() : value;
  234.             attrib = "";
  235.             value  = "";
  236.             bAttribReady = false;
  237.             bAccumValue  = false;
  238.          }
  239.          break;
  240.       }
  241.  
  242.       // Accumulate characters; if bAccumValue is true, we're on the
  243.       // right side of an "=", otherwise we're on the left side or accumulating
  244.       // a singleton name token.  I don't think quoted regions make sense
  245.       // on the left side either.
  246.       //
  247.       if ( !bInsideQuote && !bAccumValue )
  248.       {
  249.          // first skip to white after tag name <xxxx
  250.          if ( bSkipToWhite && !isWhite( currentChar ) )
  251.             continue;
  252.  
  253.          bSkipToWhite = false;
  254.  
  255.          // Whitespace not inside quotes; if we're accumulating
  256.          // an attribute, it's ready (the whitespace terminates it);
  257.          if ( isWhite( currentChar ) )
  258.          {
  259.             bAttribReady = attrib != "";
  260.          }
  261.          else
  262.          {
  263.             // Non-white space; if we have an equals sign, switch
  264.             // over to accumulate the value
  265.             if ( currentChar == '=' )
  266.             {
  267.                bAttribReady = attrib != "";
  268.                bAccumValue  = true;
  269.                MM_assert( bAttribReady, MSG_ParseErrUnexpectedEQU );
  270.             }
  271.             else
  272.             {
  273.                // Unquoted non-white non-value -- accumulate
  274.                // as name token.  If there's a name token ready,
  275.                // save it as a singleton first.
  276.                //
  277.                if ( bAttribReady )
  278.                {
  279.                   arrAttribs[ arrIdx++ ] = bMakeUpper ? attrib.toUpperCase() : attrib;
  280.                   attrib = "";
  281.                   bAttribReady = false;
  282.                }
  283.  
  284.                attrib += currentChar;
  285.             }
  286.          }
  287.       }
  288.       else
  289.       {
  290.          // We're accumulating a value
  291.          //
  292.          MM_assert( bAttribReady, MSG_ParseErrUnexpectedEQU );
  293.  
  294.          if ( !bInsideQuote && isWhite( currentChar ) )
  295.          {
  296.             // Swallow whitespace until we either get a value
  297.             // or we terminate
  298.  
  299.             if ( value != "" || bValueIsEmpty )
  300.             {
  301.                arrAttribs[ arrIdx ]  = bMakeUpper ? attrib.toUpperCase() : attrib;
  302.                arrValues[ arrIdx++ ] = bMakeUpper ? value.toUpperCase() : value;
  303.                attrib = "";
  304.                value  = "";
  305.                bAttribReady  = false;
  306.                bAccumValue   = false;
  307.                bValueIsEmpty = false;
  308.             }
  309.          }
  310.          else
  311.          {
  312.             // We're inside a quote, or we're not terminated -- keep
  313.             // accumulating
  314.             //
  315.             value += currentChar;
  316.          }
  317.       }
  318.    }
  319.  
  320.    // We're done; package up our arrays and return them
  321.    //
  322.    MM_assert( !bAccumValue, MSG_ParseErrValue );
  323.    return new Array( arrAttribs, arrValues );
  324. }
  325.  
  326. // findCombinableParent()
  327. //
  328. // Return a parent node with which the given node may have
  329. // its attributes combined with.  This routine trusts that
  330. // caller has verified that the combineTagName is a member
  331. // of combinableTagCandidates!  A combinable parent is
  332. // a direct parent up the tree who is the parent of no
  333. // other children (which would not want to inherit the
  334. // characteristics of the given child whose attributes
  335. // will migrate up), e.g.:
  336. //
  337. // <FONT face="arial"><FONT color="blue">text</FONT></FONT>
  338. //
  339. // and
  340. //
  341. // <FONT face="arial"><B><FONT color="blue">text</FONT></B></FONT>
  342. //
  343. // are combinable, but
  344. //
  345. // <FONT face="arial"><B>x<FONT color"blue">text</FONT></B></FONT>
  346. //
  347. // is not as the 'x' textual child should not inherit the
  348. // blue characteristic.  This routine walks the "direct"
  349. // (childNodes.length == 1) parent chain.
  350. //
  351. function findCombinableParent( node, combineTagName )
  352. {
  353.    MM_assert( arrayContains(combinableTagCandidates, combineTagName ) );
  354.  
  355.    var rtnNode = null;
  356.  
  357.    while ( (node.parentNode != null)    &&
  358.         (node.parentNode.childNodes.length == 1) )
  359.    {
  360.       if ( combineTagName == node.parentNode.tagName ) {
  361.          rtnNode = node.parentNode;
  362.          break;
  363.       }
  364.       if ( node.parentNode.innerHTML == node.outerHTML ) {// parent contains only this child tree
  365.          node = node.parentNode;
  366.       } else {
  367.          break;
  368.       }
  369.    }
  370.  
  371.    return rtnNode;
  372. }
  373.  
  374. // hasRedundantParent()
  375. //
  376. // Return true if the given node is redundant with a
  377. // controlling parent.  Redundant parent/children must
  378. // have identical attribute/value sets.
  379. //
  380. function hasRedundantParent( node )
  381. {
  382.    var rc = false;
  383.  
  384.    if ( arrayContains(redundantTagCandidates,  node.tagName ) )
  385.    {
  386.       var parent  = node.parentNode;
  387.  
  388.       // Find controlling parent
  389.       while( parent != null )
  390.       {
  391.          if ( node.tagName == parent.tagName )
  392.          {
  393.             // Compare parent and child attribute name/value pairs
  394.             var cArrs   = parseAttributes( node, true, true );
  395.             var pArrs   = parseAttributes( parent, true, true );
  396.             var cNames  = cArrs[0];
  397.             var cValues = cArrs[1];
  398.             var pNames  = pArrs[0];
  399.             var pValues = pArrs[1];
  400.  
  401.             if ( cNames.length == pNames.length && cValues.length == pValues.length )
  402.             {
  403.                cNames.sort();
  404.                pNames.sort();
  405.                cValues.sort();
  406.                pValues.sort();
  407.  
  408.                var len = cNames.length;
  409.                for( var i = 0; i < len; i++ )
  410.                {
  411.                   // note in js: undefined == undefined is true
  412.                   if ( pNames[i]  != cNames[i] || cValues[i] != pValues[i] )
  413.                      break;
  414.                }
  415.  
  416.                rc = (i == len); // if we got through everything they're the same
  417.             }
  418.  
  419.             if ( rc )  // if we're redundant, we're done
  420.                break;
  421.  
  422.             // Otherwise, if we're not actually overriding anything on this
  423.             // parent, we may still be redundant with an uber parent.  Cycle through
  424.             // the child's attributes and if none are present on parent keep going
  425.             //
  426.             var bKeepGoing = true;
  427.             for( var i = 0; i < cNames.length; i++ )
  428.             {
  429.                if ( arrayContains(pNames,  cNames[i] ) )
  430.                {
  431.                   bKeepGoing = false;
  432.                   break;
  433.                }
  434.             }
  435.  
  436.             if ( !bKeepGoing )
  437.                break;
  438.          } else if ( node.tagName == 'TABLE' || parent.tagName == 'TABLE') {
  439.             break;
  440.          }
  441.  
  442.          parent = parent.parentNode;
  443.       }
  444.    }
  445.  
  446.    return rc;
  447. }
  448.  
  449. // isAllWhiteNodeSignificant()
  450. //
  451. // Given a node whose inner html is all white, this
  452. // routine examines the node's siblings and returns
  453. // true if the whitespace is significant and false
  454. // otherwise.
  455. //
  456. function isAllWhiteNodeSignificant( node )
  457. {
  458.  
  459.    if (!node.parentNode) return false;
  460.  
  461.    var siblings   = node.parentNode.childNodes;
  462.    var nSiblings  = siblings.length;
  463.    var siblingIdx = 0;
  464.  
  465.    // If we're an only child, then we really need
  466.    // to look at uncles and aunts.
  467.    if ( (nSiblings == 1) && (node.parentNode != null) && (node.parentNode.nodeType != Node.DOCUMENT_NODE) )
  468.       return( isAllWhiteNodeSignificant( node.parentNode ) );
  469.  
  470.    // Find self as parent's child first
  471.    for( ; siblingIdx < nSiblings; siblingIdx++ )
  472.       if ( siblings.item( siblingIdx ) == node )
  473.          break;
  474.  
  475.    MM_assert( siblingIdx < nSiblings, MSG_ErrParentChild );
  476.  
  477.    // If sibling to the left has trailing whitespace,
  478.    // our current all white node isn't significant.  Note
  479.    // we can just look to our immediate left rather than go
  480.    // to zero because any empty siblings to the left will
  481.    // have already been gobbled.
  482.    //
  483.    var lSibling = siblingIdx > 0 ? siblings.item( siblingIdx - 1) : null;
  484.    if ( lSibling != null )
  485.    {
  486.       if ( lSibling.nodeType == Node.TEXT_NODE )
  487.       {
  488.          if ( (lSibling.data.length > 0) &&
  489.               isWhite( lSibling.data[ lSibling.data.length - 1 ] ) )
  490.             return false;
  491.       }
  492.       else
  493.       if ( lSibling.nodeType == Node.ELEMENT_NODE )
  494.       {
  495.          // non text left sibling
  496.          if ( (lSibling.innerHTML.length > 0) &&
  497.               isWhite( lSibling.innerHTML[ lSibling.innerHTML.length - 1 ] ) )
  498.             return false;
  499.       }
  500.       // else go on to our right to determine our significance
  501.    }
  502.  
  503.    // Now see if there's significant leading whitespace to
  504.    // the immediate right that might render our all white
  505.    // current node insignificant
  506.    //
  507.    var rSibling = null;
  508.    siblingIdx++;
  509.    while( siblingIdx < nSiblings )
  510.    {
  511.       rSibling = siblings.item( siblingIdx );
  512.  
  513.       if ( rSibling.nodeType == Node.TEXT_NODE )
  514.       {
  515.          // We have a textual sibling to the right; if
  516.          // this guy doesn't have leading whitespace,
  517.          // we're significant, otherwise we're not.
  518.          if ( rSibling.data.length > 0 )
  519.             return( !isWhite( rSibling.data[0] ) );
  520.  
  521.          // else empty text node
  522.       }
  523.       else
  524.       if ( rSibling.nodeType == Node.ELEMENT_NODE )
  525.       {
  526.          // We have a non-empty non-text node to the
  527.          // right; if this guy doesn't have leading
  528.          // whitespace we're significant, otherwise not
  529.          if ( rSibling.innerHTML.length > 0 )
  530.             return( !isWhite( rSibling.innerHTML[0] ) );
  531.  
  532.          // else empty non-text node...
  533.       }
  534.  
  535.       siblingIdx++;
  536.    }
  537.  
  538.    // If we got here there's nothing interesting to the
  539.    // right of this all white node, so it's as if we're
  540.    // an only child.  The DOCUMENT_NODE check is just for
  541.    // safety; there shouldn't be a way to get that high on
  542.    // empty markup node removal...
  543.  
  544.    if ( node.parentNode != null && node.nodeType != Node.DOCUMENT_NODE )
  545.       return( isAllWhiteNodeSignificant( node.parentNode ) );
  546.  
  547.    // otherwise nothing left -- we really are insignificant...
  548.    return false;
  549. }
  550.  
  551. // isRemovableEmptyTag()
  552. //
  553. // Return true if this tag can be safely removed from the
  554. // document, false otherwise.
  555. //
  556. function isRemovableEmptyTag( tagNode )
  557. {
  558.    // First this tag must be an empty removal candidate with no class info
  559.    //
  560.    if ( arrayContains(emptyRemovalCandidates,  tagNode.tagName ) && !hasClassAttribute( tagNode ) )
  561.    {
  562.       // Short-circuit for named anchor tags; empty named anchors
  563.       // should be left alone
  564.       if ( "A" == tagNode.tagName && (null != tagNode.getAttribute( "NAME" )) )
  565.          return false;
  566.  
  567.       // If the innerHTML length is zero, it's empty and
  568.       // can be safely removed *unless* it's a heading
  569.       // tag -- the first empty heading tag after text
  570.       // forces a carriage return.
  571.       //
  572.       if ( tagNode.innerHTML.length == 0 )
  573.       {
  574.          return true;
  575.       }
  576.       else
  577.       if ( isAllWhite( tagNode.innerHTML ) && !isAllWhiteNodeSignificant( tagNode ) )
  578.       {
  579.          // All empty tag candidates (generally character markup)
  580.          // spanning only whitespace can also be removed if the
  581.          // tag is not within text, or if the tag to the right of
  582.          // text that ends in whitespace or to the left of text
  583.          // that begins with whitespace....
  584.          return true;
  585.       }
  586.    }
  587.  
  588.    return false;
  589. }
  590.  
  591. // Using a tracing image in Dreamweaver attaches up to four proprietary
  592. // attributes to the body tag. We want to remove these attributes if Remove
  593. // Dreamweaver Comments is checked.
  594. //
  595. function removeTracingAttrs()
  596. {
  597.   var bodyNode = dreamweaver.getDocumentDOM('document').body;
  598.  
  599.   //look for tracing attributes - if any are found, toggle
  600.   //the global boolean to true and remove all attributes
  601.   if (cbRemoveDWComments.checked){
  602.     if (bodyNode.getAttribute("tracingsrc") ||
  603.         bodyNode.getAttribute("tracingopacity") ||
  604.         bodyNode.getAttribute("tracingx") ||
  605.         bodyNode.getAttribute("tracingy"))
  606.    {
  607.      //remove all tracing image attributes
  608.      bodyNode.removeAttribute("tracingsrc");
  609.      bodyNode.removeAttribute("tracingopacity");
  610.      bodyNode.removeAttribute("tracingx");
  611.      bodyNode.removeAttribute("tracingy");
  612.      bRemovedTracing=true;
  613.    }
  614.   }
  615. }
  616.  
  617. // hasStyleAttribute()
  618. //
  619. // Return true if the given ELEMENT tag has a STYLE set
  620. //
  621. function hasStyleAttribute( tagNode )
  622. {
  623.    return( tagNode.getAttribute( strStyleAttrib ) != null );
  624. }
  625.  
  626. // hasClassAttribute()
  627. //
  628. // Return true if the given ELEMENT tag has a CLASSID set
  629. //
  630. function hasClassAttribute( tagNode )
  631. {
  632.    return( tagNode.getAttribute( strClassAttrib ) != null );
  633. }
  634.  
  635. // loadCommentOffsets()
  636. //
  637. // This callback is used by the comment removal traversal
  638. // to push offsets of comment nodes into the userData variable
  639. // passed by the comment removal pass.  Depending on the
  640. // user's options, we might be deleting non-Dreamweaver comments,
  641. // or DW comments that are actually represented as comments.
  642. //
  643. function loadCommentOffsets( commentNode, userData )
  644. {
  645.    // MM_note( "Processing NDW comment:" + commentNode.data );
  646.  
  647.    // Server-side include comments of the form "<!-- #include... -->"
  648.    // should always be left alone!
  649.  
  650.    // eat up any leading white in comment data
  651.    var i;
  652.    for( i = 0; i < commentNode.data.length; i++ )
  653.       if ( !isWhite( commentNode.data.charAt( i ) ) )
  654.          break;
  655.  
  656.    var bIsDWComment = arrayContains(arrDWCommentNonTags, commentNode.data);
  657.  
  658.    if (cbRemoveComments.checked)
  659.    {
  660.       // if we have a #include skip it, otherwise push offsets for
  661.       // removal
  662.       //
  663.       var bSkipSSIinclude = commentNode.data.substr( i, 8 ).toLowerCase() == "#include";
  664.       var bSkipSSIecho = commentNode.data.substr( i, 5 ).toLowerCase() == "#echo";
  665.       var bSkipFWtable = commentNode.data.substr( i, 7 ).toLowerCase() == "fwtable";
  666.       var bSkipXML = commentNode.data.substr( i, 3 ).toLowerCase() == "xml";
  667.       var bSkipDoctype = commentNode.data.substr( i, 7 ).toLowerCase() == "doctype";
  668.       var bSkipFWBeginCopy = (commentNode.data.indexOf("BEGIN COPYING THE HTML") != -1);
  669.       var bSkipFWEndCopy = (commentNode.data.indexOf("STOP COPYING THE HTML HERE") != -1);
  670.       var isComment = commentNode.data.charAt(0) != "<";
  671.  
  672.       if ( !bSkipSSIinclude && !bSkipSSIecho && !bSkipDoctype && !bSkipXML && isComment && !bSkipFWtable && !bSkipFWBeginCopy && !bSkipFWEndCopy && !bIsDWComment)
  673.          userData.push( dreamweaver.nodeToOffsets( commentNode ) );
  674.    }
  675.  
  676.    if (cbRemoveDWComments.checked && bIsDWComment)
  677.    {
  678.          userData.push( dreamweaver.nodeToOffsets( commentNode ) );
  679.    }
  680.  
  681.    return true;
  682. }
  683.  
  684. // processElement()
  685. //
  686. // Process a node of ELEMENT type within the user's document
  687. // This is a callback from traverse() used during the main
  688. // removal traversal.
  689. //
  690. function processElement( elementNode )
  691. {
  692.    // MM_note( "Processing element: " + elementNode.tagName );
  693.    // Remove specific tag(s) check
  694.    //
  695.    if ( cbRemoveTags.checked &&
  696.         arrayContains(arrTagsToRemove,  elementNode.tagName ) )
  697.    {
  698.       // MM_note( "* Removing specified tag " + elementNode.outerHTML );
  699.       if ( elementNode.outerHTML == elementNode.innerHTML )
  700.          elementNode.outerHTML = "";
  701.       else
  702.          elementNode.outerHTML = elementNode.innerHTML;
  703.  
  704.       numTagsRemoved++;
  705.    }
  706.    else
  707.    {
  708.       // Don't touch tags with style information
  709.       //
  710.       if ( !hasStyleAttribute( elementNode ) )
  711.       {
  712.          // Empty tag check
  713.          //
  714.  
  715.          if ( cbRemoveEmptyTags.checked &&
  716.          (isRemovableEmptyTag( elementNode )))
  717.          {
  718.             var parent = elementNode.parentNode;
  719.  
  720.             // MM_note( "* Removing empty tag: " + elementNode.outerHTML );
  721.             elementNode.outerHTML = "";
  722.             numEmptyRemoved++;
  723.  
  724.             // Small work around DW behavior -- paragraph tags with
  725.             // children are considered "not collapsable" even if the
  726.             // children are empty.  When we remove all empty children
  727.             // of a p tag then, DW sticks in a   to keep the
  728.             // remaining <p> from being collapsed -- this makes the <p>
  729.             // then come alive in the browser layout.  So if we've just
  730.             // zapped the last child of a p tag, rewrite the P tag without
  731.             // the   so it remains collapsed in the browser layout.
  732.             // Note that if the p tag originally had text or an  
  733.             // it would still have textual children after the empty tag
  734.             // removal and would be untouched.
  735.             //
  736.             if ( parent.tagName == "P" && !(parent.hasChildNodes()) )
  737.                parent.outerHTML = "<p>";
  738.          }
  739.          // Redundant child check
  740.          //
  741.          else
  742.          if ( cbRemoveRedundant.checked &&
  743.               hasRedundantParent( elementNode ) )
  744.          {
  745.             // MM_note( "* Removing redundant tag: " + elementNode.outerHTML );
  746.             elementNode.outerHTML = elementNode.innerHTML;
  747.             numRedundantRemoved++;
  748.          }
  749.          // Child/parent coalesce check
  750.          //
  751.          else
  752.          if ( cbCombineFonts.checked &&
  753.               arrayContains(combinableTagCandidates,  elementNode.tagName ) )
  754.          {
  755.             var parent  = findCombinableParent( elementNode, elementNode.tagName );
  756.             if ( parent != null )
  757.             {
  758.                // MM_note( "* Combining font tags: " + elementNode.outerHTML );
  759.  
  760.                // Set all child attributes on parent and remove child
  761.                //
  762.                var arrs    = parseAttributes( elementNode, true, false );
  763.                var attribs = arrs[0];
  764.                var values  = arrs[1];
  765.  
  766.                for( var i = 0; i < attribs.length; i++ )
  767.                   parent.setAttribute( attribs[i], values[i] ); // The value part
  768.                                                                 // here may be null
  769.                elementNode.outerHTML = elementNode.innerHTML;
  770.                numFontsCombined++;
  771.             }
  772.          }
  773.          // Dreamweaver comment check -- dreamweaver comments
  774.          // come back to us as element nodes rather than comment nodes
  775.          else
  776.          if ( cbRemoveDWComments.checked &&
  777.               arrayContains(arrDWCommentTags, elementNode.tagName ) )
  778.          {
  779.             // MM_note( "Removing DW comment: " + elementNode.tagName );
  780.             dreamweaver.editLockedRegions(true);
  781.             elementNode.outerHTML = elementNode.innerHTML;
  782.             numCommentsRemoved++;
  783.          }
  784.          else
  785.          if ( cbRemoveDWComments.checked &&
  786.               arrayContainsPrefix(arrDWTagPrefixes, elementNode.tagName ) )
  787.          {
  788.             // MM_note( "Removing DW tag: " + elementNode.tagName );
  789.             dreamweaver.editLockedRegions(true);
  790.  
  791.       // sn 8/6/01: for some reason, for empty tags, innerHTML
  792.       // returns the entire tag instead of the empty string.  (Anyway,
  793.       // this is what I observed for MMTInstance:Param tags.)  Tweaking
  794.       // to use "" instead of elementNode.innerHTML for such tags.
  795.       if ( elementNode.tagName == "MMTEMPLATE:EXPR"            ||
  796.          elementNode.tagName == "MMTEMPLATE:PARAM"           ||
  797.          elementNode.tagName == "MMTEMPLATE:PASSTHROUGHEXPR" ||
  798.          elementNode.tagName == "MMTINSTANCE:PARAM"          )
  799.         elementNode.outerHTML = "";
  800.       else
  801.         elementNode.outerHTML = elementNode.innerHTML;
  802.             numTagsRemoved++;
  803.          }
  804.       }
  805.    }
  806.  
  807.    return true; // continue traverse
  808. }
  809.  
  810. // emptyHeaderStateTextHandler()
  811. //
  812. // This text node callback is used by pass two to flip
  813. // the global bPreserveEmptyHeader state to true -- we
  814. // just encountered text, so the next empty header
  815. // found will force a carriage return and thus can't
  816. // be removed.  Empty headers after that however can
  817. // be removed until the next piece of text is encountered...
  818. //
  819. function emptyHeaderStateTextHandler( node )
  820. {
  821.    bPreserveEmptyHeader = true;
  822.    return true;
  823. }
  824.  
  825. // traverse()
  826. //
  827. // Do a recursive depth-first traversal of the user's
  828. // document starting from the given node.
  829. //
  830. // Callers provide up to three callback functions which
  831. // accept a node argument, one each (or the same one)
  832. // to process nodes of ELEMENT, TEXT, or COMMENT type.
  833. // At least one callback function is required.
  834. //
  835. // The handlers may stop the traversal by returning false;
  836. // returning true will continue the traversal to its
  837. // completion.
  838. //
  839. // A fourth argument, a handle to a some user variable to
  840. // be passed on to each callback, may also be provided.
  841. //
  842. function traverse( node, fElementHandler ) // optional: fTextHandler, fCommentHandler, userData )
  843. {
  844.    var fTextHandler  = traverse.arguments.length >= 3 ? traverse.arguments[2] : null;
  845.    var fCmmtHandler  = traverse.arguments.length >= 4 ? traverse.arguments[3] : null;
  846.    var userData      = traverse.arguments.length >= 5 ? traverse.arguments[4] : null;
  847.    var children      = node.childNodes;
  848.    var nChildren     = children.length;
  849.    var bContinue     = true;
  850.    var current       = null;
  851.  
  852.    for( var i = 0; bContinue && (i < nChildren); i++ )
  853.    {
  854.       current = children.item( i );
  855.  
  856.       // descend to any children first
  857.       if ( current.hasChildNodes() )
  858.          traverse( current, fElementHandler, fTextHandler, fCmmtHandler, userData );
  859.  
  860.       // process current node
  861.       switch( current.nodeType )
  862.       {
  863.          case Node.ELEMENT_NODE:
  864.             if ( userData != null )
  865.                bContinue = fElementHandler( current, userData );
  866.             else
  867.                bContinue = fElementHandler( current );
  868.             break;
  869.  
  870.          case Node.COMMENT_NODE:
  871.             if ( fCmmtHandler != null )
  872.                if ( userData != null )
  873.                   bContinue = fCmmtHandler( current, userData );
  874.                else
  875.                   bContinue = fCmmtHandler( current );
  876.             break;
  877.  
  878.          case Node.TEXT_NODE:
  879.             if ( fTextHandler != null )
  880.                if ( userData != null )
  881.                   bContinue = fTextHandler( current, userData )
  882.                else
  883.                   bContinue = fTextHandler( current )
  884.             break;
  885.  
  886.          case Node.DOCUMENT_NODE:
  887.          default:
  888.              MM_error( MSG_UnknownNodeType, current.nodeType );
  889.       }
  890.    }
  891. }
  892.  
  893. // doPassOne()
  894. //
  895. // Pass one does cleanup based on the HTML source string for
  896. // the user's document; currently that means comment and extra
  897. // whitespace removal.  Note that most DW comments are not handled
  898. // here (since they're internally represented as tags), but some
  899. // are.
  900. //
  901. // BW 8/17/98 Removed "remove extra whitespace" option for
  902. //            performance reasons
  903. //
  904. function doPassOne()
  905. {
  906.    if ( cbRemoveComments.checked || cbRemoveDWComments.checked )  // pass one options
  907.    {
  908.       var htmlstr = dreamweaver.getDocumentDOM( 'document' ).documentElement.outerHTML;
  909.       var htmlpos = 0;
  910.       var htmlarr = new Array(); // array to save newing of intermediate
  911.                                  // string copies of doc
  912.  
  913.       // To remove comments, traverse over the entire DOM gathering
  914.       // offsets into the HTML source of the comments to be removed,
  915.       // then remove those comments from the HTML source string.
  916.       //
  917.       var root           = dreamweaver.getDocumentDOM('document');
  918.       var commentOffsets = new Array();
  919.       var stubCallback   = new Function( "node", "userData", "return true;" );
  920.  
  921.       if ( root != null && root.hasChildNodes() )
  922.          traverse( root
  923.                  , stubCallback
  924.                  , stubCallback
  925.                  , loadCommentOffsets
  926.                  , commentOffsets );
  927.  
  928.       // Now use offsets to delete sections of text from
  929.       // within the document source string.
  930.       //
  931.       if ( commentOffsets.length > 0 )
  932.       {
  933.          var lastpos = 0;
  934.          for( var i = 0; i < commentOffsets.length; i++ )
  935.          {
  936.             htmlarr[htmlpos++] = htmlstr.substring( lastpos
  937.                                                   , commentOffsets[i][0] );
  938.             lastpos = commentOffsets[i][1];
  939.             numCommentsRemoved++;
  940.          }
  941.  
  942.          htmlarr[htmlpos++] = htmlstr.substring( lastpos );
  943.       }
  944.  
  945.       if ( htmlarr.length > 0 )
  946.          dreamweaver.getDocumentDOM( 'document' ).documentElement.outerHTML = htmlarr.join("");
  947.    }
  948. }
  949.  
  950. // doPassTwo()
  951. //
  952. // Pass two does cleanup on DOM objects as appropriate over the
  953. // course of traversing the DOM heirarchy.  The actual work in this
  954. // pass is done in the processElement() callback.
  955. //
  956. function doPassTwo()
  957. {
  958.    // Load up comma-separated list of tags to remove if any; warn
  959.    // if option is checked but no tags specified
  960.    //
  961.    arrTagsToRemove = dreamweaver.getTokens( tbTagsToRemove.value.toUpperCase(), ", " );
  962.    if ( cbRemoveTags.checked && arrTagsToRemove.length == 0 )
  963.       MM_error( MSG_NoTagsToRemove );
  964.  
  965.    // Traverse document, processing leaves
  966.    //
  967.    var root = dreamweaver.getDocumentDOM('document');
  968.  
  969.    if ( root != null && root.hasChildNodes() )
  970.    {
  971.       traverse( root
  972.               , processElement
  973.               , emptyHeaderStateTextHandler )
  974.  
  975.       // and finally attempt to remove tracingsrc attributes
  976.       // in body tag
  977.       //
  978.       removeTracingAttrs();
  979.    }
  980.    else
  981.       MM_error( MSG_ErrEmptyDoc );
  982.  
  983. }
  984.  
  985. // cleanUpDocument()
  986. //
  987. // Main routine for performing clean up when user hits OK.
  988. // Clean up is done in three passes:
  989. //
  990. // Pass 1: Clean up certain items based on the entire HTML
  991. //         document as a string
  992. // Pass 2: Clean up certain items while traversing the DOM
  993. //
  994. function cleanUpDocument()
  995. {
  996.    // Set up logging particulars
  997.    //
  998.    if ( cbShowLog.checked )
  999.    {
  1000.       MM_enableLogging();
  1001.       MM_clearLog();
  1002.    }
  1003.    else {
  1004.       MM_disableLogging();
  1005.    }
  1006.  
  1007.    // Do cleanup in two passes -- the first pass , the second pass
  1008.    // cleans up certain items based on a hierarchy traversal of the DOM.
  1009.    // Then, if the doc is XHTML, call the cleanupXHTML function.
  1010.    //
  1011.    MM.setBusyCursor();
  1012.    doPassOne();
  1013.    doPassTwo();
  1014.    if (dw.getDocumentDOM().getIsXHTMLDocument())
  1015.       arrXHTMLresults = dw.getDocumentDOM().cleanupXHTML(true);
  1016.    else
  1017.       arrXHTMLresults = 0;
  1018.    MM.clearBusyCursor();
  1019.    finalize();
  1020. }
  1021.  
  1022. // initialize()
  1023. //
  1024. // This is called on BODY onLoad; initialize all script globals
  1025. //
  1026. function initialize()
  1027. {
  1028.    // Counters for logging output
  1029.    //
  1030.    numEmptyRemoved      = 0;
  1031.    numRedundantRemoved  = 0;
  1032.    numTagsRemoved       = 0;
  1033.    numCommentsRemoved   = 0;
  1034.    numFontsCombined     = 0;
  1035.    bRemovedTracing      = false;
  1036.  
  1037.    arrTagsToRemove.length = 0; // Empty array
  1038.  
  1039.    strClassAttrib       = "CLASS";
  1040.    strStyleAttrib       = "STYLE";
  1041.  
  1042.    // The following tags represent the tag names of Dreamweaver-
  1043.    // specific comments, which are processed through the Dreamweaver
  1044.    // JS API/DOM as named element nodes rather than comment nodes
  1045.    //
  1046.    arrDWCommentTags.push ( "MM:EDITABLE"
  1047.                          , "MM:LIBITEM"       // variable library item (currently unused)
  1048.                          , "MM:TEMPLATE"
  1049.                          , "{#CUSTOMOBJ}"
  1050.                          , "{#IMEINLINE}"      // used by Japanese DW
  1051.                          , "{#LIBITEM}" );
  1052.  
  1053.    // The following strings are prefixes; any tag beginning with one of
  1054.    // these prefixes is Dreamweaver-specific markup.  (These represent
  1055.    // tags, not comments, but we still remove them when removing Dreamweaver-
  1056.    // specific comments.)
  1057.    //
  1058.    // NOTE (sn 6/22/01): by the time we get a tag name to compare with,
  1059.    // it's been converted to uppercase.  So, I'm including uppercase versions
  1060.    // of these strings.  However, I'm leaving the mixed-case versions in as
  1061.    // well, in case this ever changes.
  1062.    arrDWTagPrefixes.push ( "MMTemplate:", "MMTInstance:",
  1063.                  "MMTEMPLATE:", "MMTINSTANCE:" );
  1064.  
  1065.    // This array contains Dreamweaver comments that are actually
  1066.    // internally stored as comments (i.e., they aren't magically
  1067.    // transformed into tags the way the above tags are).
  1068.    arrDWCommentNonTags.push ( "#DefaultLayoutTable","DWLayoutDefaultTable","DWLayoutEmptyCell","DWLayoutTable" );
  1069.  
  1070.    // The following tags can be harmlessly removed from the user's
  1071.    // document if they're empty.  Note that the Heading tags are
  1072.    // not always safe and require special further handling; see
  1073.    // isEmptyRemoveableTag().
  1074.    //
  1075.    emptyRemovalCandidates.push ( "H1", "H2", "H3", "H4", "H5", "H6"
  1076.                                , "TT", "I", "B", "U", "STRIKE", "BIG"
  1077.                                , "SMALL", "SUB", "SUP", "EM", "STRONG"
  1078.                                , "DFN", "CODE", "SAMP", "KBD", "VAR"
  1079.                                , "CITE", "XMP", "BLINK"
  1080.                                , "ADDRESS"
  1081.                                , "A"
  1082.                                , "FONT"
  1083.                                , "SPAN"
  1084.                                , "TABLE"
  1085.                                , "BLOCKQUOTE"
  1086.                                , "LI", "OL", "UL"
  1087.                                , "DD", "DT", "DL"
  1088.                                , "DIR", "MENU"
  1089.                                , "DIV", "CENTER" );
  1090.  
  1091.    // These tags can be safely removed if they're redundant
  1092.    // with their immediate parent, i.e., this tags have
  1093.    // no nesting semantics.
  1094.    //
  1095.    redundantTagCandidates.push( "TT", "I", "B", "U", "S", "STRIKE", "BIG"
  1096.                               , "SMALL", "SUB", "SUP", "EM", "STRONG"
  1097.                               , "DFN", "CODE", "SAMP", "KBD", "VAR"
  1098.                               , "CITE", "XMP"
  1099.                               , "FONT"
  1100.                               , "CENTER"
  1101.                               , "SPAN" );
  1102.  
  1103.    // These tags can be safely coalesced with parents with identical
  1104.    // regions of influence.  Currently this is only done for FONT tags.
  1105.    //
  1106.    combinableTagCandidates.push( "FONT" );
  1107.  
  1108.  
  1109.    // Global used by pass two to indicate if the next empty
  1110.    // header we encounter should be preserved -- the first
  1111.    // empty header after text is significant as a carriage
  1112.    // return is forced; after that they can be gobbled until
  1113.    // there's more text.
  1114.    //
  1115.    bPreserveEmptyHeader = false;
  1116.  
  1117.  
  1118.    // And finally reference actual form element names
  1119.    // here once
  1120.    //
  1121.    with( document.optionsForm )
  1122.    {
  1123.       cbRemoveEmptyTags       = removeEmptyTags;
  1124.       cbRemoveRedundant       = removeRedundantChildren;
  1125.       cbRemoveComments        = removeNDWComments;
  1126.       cbRemoveDWComments      = removeDWComments;
  1127.       cbRemoveTags            = removeTag;
  1128.       cbCombineFonts          = combineFonts;
  1129.       cbShowLog               = showLog;
  1130.       tbTagsToRemove          = tagsToRemove;
  1131.    }
  1132. }
  1133.  
  1134.  
  1135.  
  1136. function finalize()
  1137. {
  1138.    // Show what we did if show log is enabled
  1139.    //
  1140.    if ( cbShowLog.checked )
  1141.    {
  1142.       MM_note( MSG_TrcSummaryHeader );
  1143.     var bLeftSomethingUnfixed =
  1144.  
  1145.           (arrXHTMLresults.length > 0) &&
  1146.           (
  1147.               (arrXHTMLresults[1] > 0)   ||
  1148.               (arrXHTMLresults[2] > 0)   ||
  1149.               (arrXHTMLresults[3] > 0)   ||
  1150.               (arrXHTMLresults[4] > 0)   ||
  1151.               (arrXHTMLresults[5] > 0)
  1152.           );
  1153.  
  1154.       var bDidSomething = (numEmptyRemoved > 0)      ||
  1155.                           (numRedundantRemoved > 0)  ||
  1156.                           (numTagsRemoved > 0)       ||
  1157.                           (numCommentsRemoved > 0)   ||
  1158.                           (numFontsCombined > 0)     ||
  1159.               (arrXHTMLresults[0] > 0)   ||
  1160.                           (bRemovedTracing);
  1161.  
  1162.       if ( bDidSomething || bLeftSomethingUnfixed )
  1163.       {
  1164.          if ( numEmptyRemoved > 0 )
  1165.             MM_note( MSG_TrcEmptyRemoved, numEmptyRemoved );
  1166.          if ( numRedundantRemoved > 0 )
  1167.             MM_note( MSG_TrcRedundantRemoved, numRedundantRemoved );
  1168.          if ( numTagsRemoved > 0 )
  1169.             MM_note( MSG_TrcTagsRemoved, numTagsRemoved );
  1170.          if ( numCommentsRemoved > 0 )
  1171.             MM_note( MSG_TrcCommentsRemoved, numCommentsRemoved );
  1172.          if ( numFontsCombined > 0 )
  1173.             MM_note( MSG_TrcFontsCombined, numFontsCombined );
  1174.          if ( bRemovedTracing )
  1175.             MM_note( MSG_TracingAttrsRemoved );
  1176.          if ( arrXHTMLresults[0] > 0)
  1177.             MM_note( MSG_XHTMLFixed );
  1178.  
  1179.          if ( bDidSomething && bLeftSomethingUnfixed )
  1180.             MM_note( "\n" );
  1181.          if ( arrXHTMLresults[1] > 0)
  1182.             MM_note( MSG_XHTMLMissingMapID, arrXHTMLresults[1]);
  1183.          if ( arrXHTMLresults[2] > 0)
  1184.             MM_note( MSG_XHTMLMissingScriptType, arrXHTMLresults[2] );
  1185.          if ( arrXHTMLresults[3] > 0)
  1186.             MM_note( MSG_XHTMLMissingStyleType, arrXHTMLresults[3] );
  1187.          if ( arrXHTMLresults[4] > 0)
  1188.             MM_note( MSG_XHTMLMissingImgAlt, arrXHTMLresults[4] );
  1189.          if ( arrXHTMLresults[5] > 0)
  1190.             MM_note( MSG_XHTMLMissingAreaAlt, arrXHTMLresults[5] );
  1191.       }
  1192.       else {
  1193.          MM_note( MSG_TrcDidNothing );
  1194.       }
  1195.       MM_showLog();
  1196.    }
  1197.  
  1198.    window.close();
  1199. }
  1200.  
  1201.  
  1202. function setMenuText()
  1203. {
  1204.   if (dw.getDocumentDOM() && dw.getDocumentDOM().getIsXHTMLDocument())
  1205.     return MENU_CleanupXHTML;
  1206.   else
  1207.     return MENU_CleanupHTML;
  1208. }
  1209.